﻿package net.wangit.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import net.wangit.MapList;
import net.wangit.framework.debug.Recorder;
import net.wangit.jdbc.config.JDBCSettings;
import net.wangit.jdbc.executable.Executable;
import net.wangit.jdbc.executable.Query;
import net.wangit.jdbc.sqltype.SqlType;
import net.wangit.jdbc.table.TableMeta;
import net.wangit.jdbc.table.TableMetaFactory;
import net.wangit.jdbc.util.JDBCExceptionReporter;
import net.wangit.util.EmptyObject;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * 数据库实现类
 * 
 * @author watson Feb 12, 2010
 */
@SuppressWarnings("serial")
public class DBImpl implements DBImplementor {
	private static final Log log = LogFactory.getLog(DBImpl.class);
	private JDBCSettings js;
	private transient Batcher batcher;
	private transient Connection connection;

	public void init(JDBCSettings js) {
		this.js = js;
		this.batcher = new NonBatchingBatcher(this);
	}

	public JDBCSettings getJDBCSettings() {
		return this.js;
	}

	public Batcher getBatcher() {
		return this.batcher;
	}

	public Connection getConnection() throws JDBCException {
		if (this.connection == null) {
			try {
				this.connection = this.js.getConnectionProvider()
						.getConnection();

				Recorder.countConnection();
			} catch (SQLException e) {
				throw new JDBCException("could not open connection", e);
			}
		}
		return this.connection;
	}

	public MapList query(String sql) throws JDBCException {
		return query(sql, new QueryParameters());
	}

	public MapList query(String sql, String value, int type)
			throws JDBCException {
		return query(sql, new String[] { value }, new int[] { type });
	}

	public MapList query(String sql, String[] values, int[] types)
			throws JDBCException {
		return query(sql, new QueryParameters(values, types));
	}

	public MapList query(String sql, QueryParameters queryParameters)
			throws JDBCException {
		try {
			return Query.doQuery(this, sql, queryParameters);
		} catch (JDBCException e) {
			log.error("query error: " + sql);
			throw e;
		} catch (SQLException e) {
			log.error("query error: " + sql);
			throw new JDBCException("query error", e);
		}
	}

	public ResultSet getResultSet(String sql) throws JDBCException {
		return getResultSet(sql, new QueryParameters());
	}

	public ResultSet getResultSet(String sql, String value, int type)
			throws JDBCException {
		return getResultSet(sql, new String[] { value }, new int[] { type });
	}

	public ResultSet getResultSet(String sql, String[] values, int[] types)
			throws JDBCException {
		return getResultSet(sql, new QueryParameters(values, types));
	}

	public ResultSet getResultSet(String sql, QueryParameters queryParameters)
			throws JDBCException {
		try {
			return Query.getResultSet(this, sql, queryParameters);
		} catch (JDBCException e) {
			log.error("query error : " + sql);
			throw e;
		} catch (SQLException e) {
			log.error("query error : " + sql);
			throw new JDBCException("query error", e);
		}
	}

	public int[] save(List<Table> tables) throws JDBCException {
		int[] count = new int[3];
		for (int i = 0; i < tables.size(); ++i) {
			int[] theCount = save(tables.get(i));
			count[0] += theCount[0];
			count[1] += theCount[1];
			count[2] += theCount[2];
		}
		return count;
	}

	public int[] save(Table table) throws JDBCException {
		TableMeta meta = TableMetaFactory.getTableMeta(table.getDomain(), table
				.getTableName());
		int[] count = new int[3];
		List<ColumnRow> rows = table.getRows();
		String unitId = table.getUnitId();
		for (int i = 0; i < rows.size(); ++i) {
			((Executable) rows.get(i)).execute(this, meta, count, unitId);
		}
		return count;
	}

	public int execute(String sql) throws JDBCException {
		return execute(sql, EmptyObject.STRING_ARRAY, EmptyObject.INT_ARRAY);
	}

	public int execute(String sql, String value, int type) throws JDBCException {
		return execute(sql, new String[] { value }, new int[] { type });
	}

	public int execute(String sql, String[] values, int[] types)
			throws JDBCException {
		PreparedStatement ps = null;
		try {
			ps = this.batcher.prepareStatement(sql);
			for (int i = 0; i < types.length; ++i) {
				SqlType sqlType = this.js.getDatabase().getSqlType(types[i]);
				sqlType.set(ps, values[i], i + 1);
			}
			return ps.executeUpdate();
		} catch (SQLException sqle) {
			JDBCExceptionReporter.logExceptions(sqle);
			String msg = (new StringBuilder("execute error: ")).append(sql)
					.toString();
			throw new JDBCException(msg, sqle);
		} catch (Exception e) {
			throw new JDBCException(e.getMessage(), e);
		} finally {
			try {
				this.batcher.closeStatement(ps);
			} catch (SQLException sqle) {
				throw new JDBCException("could not close statement", sqle);
			}
		}
	}

	public void executeBatch(String sql, List<String[]> valuesList, int[] types)
			throws JDBCException {
		try {
			PreparedStatement ps = this.batcher.prepareBatchStatement(sql);
			for (int i = 0; i < valuesList.size(); ++i) {
				String[] values = valuesList.get(i);
				for (int j = 0; j < types.length; ++j) {
					SqlType sqlType = this.js.getDatabase()
							.getSqlType(types[j]);
					sqlType.set(ps, values[j], j + 1);
				}

				this.batcher.addToBatch(-1);
			}
			this.batcher.executeBatch();
		} catch (SQLException sqle) {
			this.batcher.abortBatch(sqle);
			log.error("error to execute batch:", sqle);
			throw new JDBCException("error to execute batch", sqle);
		} catch (Exception e) {
			log.error("error to execute batch:", e);
			throw new JDBCException("error to execute batch", e);
		}
	}

	public Transaction beginTransaction() throws JDBCException {
		return this.js.getConnectionProvider().getTransaction().begin(this);
	}

	public void close() throws JDBCException {
		this.batcher.closeStatements();
		try {
			if ((this.connection != null) && (!(this.connection.isClosed()))) {
				if (!(this.connection.getAutoCommit())) {
					this.connection.commit();
				}
				this.js.getConnectionProvider()
						.closeConnection(this.connection);
				if (log.isDebugEnabled())
					log.debug("close connection.");
			}
		} catch (SQLException sqle) {
			throw new JDBCException("Cannot close connection", sqle);
		}
		this.connection = null;
	}
}